﻿/*************************************************
  Copyright (C), 2014-2017, lmd Tech. Co., Ltd.
  文 件  名:	Assets\Scripts\Tools\CxExtension\MathUtil.cs
  作       者:	陈佳
  版       本:	1.0
  完成日期:	2017/08/10
  功能描述:	数学工具类,提供常用的数学函数
  主要功能: 判断两个数的差距;
				判断两个 float是否相等;
*************************************************/

using System;
using UnityEngine;

namespace CxExtension
{
	public class MathUtil
	{
		//浮点数比较的常用误差
		public const float Epsilon= 0.000001f;

		/// <summary>
		/// 判断a和b是否相等,默认精度0.001f,建议不要小于这个值,不然判断不会达到预期
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <param name="precision">误差数 默认是0.001f</param>
		/// <returns></returns>
		public static bool Equal(float a, float b, float precision = Epsilon)
		{
			return Equal((double)a,(double) b, (double)precision);
		}

		public static bool Equal(double a,double b,double precision = Epsilon)
		{
			if(precision < 0)
			{
				Debuger.ErrFormat("MathUtil.Equal precision:{0} 不能小于0",precision);
				return false;
			}
			var offset = Math.Abs(a - b);
			return offset <= precision;
		}

		/// <summary>
		/// 判断f1是否小于f2
		/// </summary>
		/// <param name="f1"></param>
		/// <param name="f2"></param>
		/// <returns></returns>
		public static bool IsLower(float f1,float f2)
		{
			return f1 - f2 <= -Epsilon;
		}

		/// <summary>
		/// 判断某个值是否在min和max之间,包含min不包含max,常用于判断索引是否越界
		/// </summary>
		/// <param name="value"></param>
		/// <param name="min"></param>
		/// <param name="max"></param>
		/// <returns></returns>
		public static bool IsRound(float value,float min,float max)
		{
			bool r = min <= value && value < max;
			return r;
		}

		/// <summary>
		/// 让v0 向 v1 以 offsetSpeed 每秒的改变量靠近,计算出在时间t之后的结果
		/// </summary>
		/// <param name="v0">当前值</param>
		/// <param name="v1">目标值</param>
		/// <param name="offsetSpeed">改变速度,必须是正数,函数会自动辨认方向</param>
		/// <param name="t">给定的时间</param>
		/// <param name="result">计算结果</param>
		/// <returns>如果发生改变了,返回true,否则返回false</returns>
		public static bool Lerp(float v0,float v1,float offsetSpeed,float t,out SLerpResult result)
		{
			bool needChange = false;
			do
			{
				//检查是否需要改变
				if(Equal(v0,v1))
				{
					break;
				}
				//检查改变速度是否满足要求
				if(offsetSpeed <= 0)
				{
					Debuger.ErrFormat("MathUtil.Lerp offsetSpeed:{0} ,不能小于等于0",offsetSpeed);
					break;
				}
				needChange = true;
			} while(false);
			//如果不需要改变就退出
			if(needChange == false)
			{
				result.speedDir = 0;
				result.v = v1;
				result.usedtime = 0;
				result.remaintime = t;
				return false;
			}

			var offset = v1 - v0;
			var usedtime = 0f;

			//检查改变速度的方向
			var speedDir = offset > 0 ? 1 : -1;
			offsetSpeed *= speedDir;

			//计算改变完成需要的时间
			var offsetTime = offset / offsetSpeed;

			//检查是否还有剩余的时间
			usedtime = t > offsetTime ? offsetTime : t;

			//计算结果
			result.speedDir = speedDir;
			result.v = v0 + offsetSpeed * usedtime;
			result.usedtime = usedtime;
			result.remaintime = t - usedtime;
			return true;
		}

		/// <summary>
		/// 解一元二次方程	 ,如果有解返回true
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <param name="c"></param>
		/// <param name="jie"></param>
		/// <returns></returns>
		public static bool Quadratic(float a,float b,float c,out float[] jie)
		{
			if(Equal(0,a))
			{
				Debuger.ErrFormat("参数错误 a:{0} 不能等于0,",a);
				jie = new float[0];
				return false;
			}

			float derta = b * b - 4 * a * c;
			float sqr = Mathf.Sqrt(derta);
			var fenz1 = -b + sqr;
			var fenz2 = -b - sqr;
			var fenm = 2 * a;
			float j1, j2;
			if(derta > 0)
			{
				j1 = fenz1 / fenm;
				j2 = fenz2 / fenm;
				jie = new float[] { j1,j2 };
			}
			else if(derta < 0)
			{
				jie = new float[0];
				return false;
			}
			else
			{
				j1 = -b;
				jie = new float[] { j1 };
			}
			return true;
		}

		/// <summary>
		/// 解一元二次方程,找出大于零最小的解  ,如果有这个解 返回true
		/// </summary>
		/// <param name="a"></param>
		/// <param name="b"></param>
		/// <param name="c"></param>
		/// <param name="jie"></param>
		/// <returns></returns>
		public static bool QuadraticMin0(float a,float b,float c,out float jie)
		{
			float[] jies;
			Quadratic(a,b,c,out jies);
			var length = jies.Length;
			if(length == 2)
			{
				var j1 = jies[0];
				var j2 = jies[1];
				if(j1 > 0 && j2 > 0)
				{
					jie = Mathf.Min(j1,j2);
					return true;
				}
				else if(j1 < 0 && j2 < 0)
				{
					jie = -1;
					return false;
				}
				else
				{
					jie = Mathf.Max(j1,j2);
					return true;
				}
			}
			else if(length == 1)
			{
				var j1 = jies[0];
				if(j1 >= 0)
				{
					jie = j1;
					return true;
				}
				else
				{
					jie = -1;
					return false;
				}
			}
			jie = -1;
			return false;
		}

		public struct SLerpResult
		{
			public float remaintime;                                      //改变完成后还剩余的时间
			public int speedDir;                                             //改变速度的方向	-1表示负改变,1表示正改变
			public float usedtime;                                         //改变所使用的时间
			public float v;                                                     //改变后的值
		}
	}
}